name: Preview (deploy)

on:
  workflow_run:
    workflows:
      - 'Preview (build)'
    types:
      - completed

jobs:
  cache-manifests-file:
    name: Cache Manifests File
    runs-on: ubuntu-latest
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    permissions:
      # "If you specify the access for any of these scopes, all of those that are not specified are set to none."
      # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
      actions: read # Access cache
    outputs:
      manifests-cache-key: ${{ steps.hash.outputs.MANIFESTS_FILE_HASH }}
      git-ref: ${{ steps.event.outputs.GIT_REF }}
      pr-number: ${{ steps.event.outputs.PR_NUMBER }}
      action: ${{ steps.event.outputs.ACTION }}
    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0
        with:
          disable-sudo: true
          egress-policy: block
          allowed-endpoints: >
            api.github.com:443

      - name: 'Download artifacts'
        # Fetch output (zip archive) from the workflow run that triggered this workflow.
        uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
        with:
          script: |
            let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
               owner: context.repo.owner,
               repo: context.repo.repo,
               run_id: context.payload.workflow_run.id,
            });
            let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
              return artifact.name == "preview-spec"
            })[0];
            if (matchArtifact === undefined) {
              throw TypeError('Build Artifact not found!');
            }
            let download = await github.rest.actions.downloadArtifact({
               owner: context.repo.owner,
               repo: context.repo.repo,
               artifact_id: matchArtifact.id,
               archive_format: 'zip',
            });
            let fs = require('fs');
            fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/preview-spec.zip`, Buffer.from(download.data));

      - name: 'Accept event from first stage'
        run: unzip preview-spec.zip event.json

      - name: Read Event into ENV
        id: event
        run: |
          echo PR_NUMBER=$(jq '.number | tonumber' < event.json) >> $GITHUB_OUTPUT
          echo ACTION=$(jq --raw-output '.action | tostring | [scan("\\w+")][0]' < event.json) >> $GITHUB_OUTPUT
          echo GIT_REF=$(jq --raw-output '.pull_request.head.sha | tostring | [scan("\\w+")][0]' < event.json) >> $GITHUB_OUTPUT

      - name: Hash Rendered Manifests File
        id: hash
        # If the previous workflow was triggered by a PR close event, we will not have a manifests file artifact.
        if: ${{ steps.event.outputs.ACTION != 'closed' }}
        run: |
          unzip preview-spec.zip manifests.rendered.yml
          ls
          echo "MANIFESTS_FILE_HASH=$(md5sum manifests.rendered.yml | awk '{ print $1 }')" >> $GITHUB_OUTPUT

      - name: Cache Manifests File
        if: ${{ steps.event.outputs.ACTION != 'closed' }}
        uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0
        with:
          path: manifests.rendered.yml
          key: ${{ steps.hash.outputs.MANIFESTS_FILE_HASH }}

      - name: DEBUG - Print Job Outputs
        if: ${{ runner.debug }}
        run: |
          echo "PR number: ${{ steps.event.outputs.PR_NUMBER }}"
          echo "Git Ref: ${{ steps.event.outputs.GIT_REF }}"
          echo "Action: ${{ steps.event.outputs.ACTION }}"
          echo "Manifests file hash: ${{ steps.hash.outputs.MANIFESTS_FILE_HASH }}"
          cat event.json

  deploy-uffizzi-preview:
    name: Deploy to Uffizzi Virtual Cluster
    needs:
      - cache-manifests-file
    if: ${{ github.event.workflow_run.conclusion == 'success' && needs.cache-manifests-file.outputs.action != 'closed' }}
    permissions:
      contents: read
      pull-requests: write
      id-token: write
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4

      # Identify comment to be updated
      - name: Find comment for Ephemeral Environment
        uses: peter-evans/find-comment@d5fe37641ad8451bdd80312415672ba26c86575e # v3
        id: find-comment
        with:
          issue-number: ${{ needs.cache-manifests-file.outputs.pr-number }}
          comment-author: 'github-actions[bot]'
          body-includes: pr-${{ needs.cache-manifests-file.outputs.pr-number }}
          direction: last

      # Create/Update comment with action deployment status
      - name: Create or Update Comment with Deployment Notification
        id: notification
        uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4
        with:
          comment-id: ${{ steps.find-comment.outputs.comment-id }}
          issue-number: ${{ needs.cache-manifests-file.outputs.pr-number }}
          body: |
            ## Uffizzi Ephemeral Environment - Virtual Cluster

            :cloud: deploying cluster `pr-${{ needs.cache-manifests-file.outputs.pr-number }}`

            :gear: Updating now by workflow run [${{ github.run_id }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}).

            Download the Uffizzi CLI to interact with the upcoming virtual cluster
            https://docs.uffizzi.com/install
          edit-mode: replace

      - name: Connect to Virtual Cluster
        uses: UffizziCloud/cluster-action@main
        with:
          cluster-name: pr-${{ needs.cache-manifests-file.outputs.pr-number }}
          server: https://app.uffizzi.com

      - name: Fetch cached Manifests File
        id: cache
        uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4
        with:
          path: manifests.rendered.yml
          key: ${{ needs.cache-manifests-file.outputs.manifests-cache-key }}

      - name: Kustomize and Apply Manifests
        id: prev
        run: |
          # Apply kustomized manifests to virtual cluster.
          export KUBECONFIG=`pwd`/kubeconfig
          kubectl apply -f manifests.rendered.yml --kubeconfig ./kubeconfig
          # Allow uffizzi to sync the resources
          sleep 10
          # Get the hostnames assigned by uffizzi
          export BACKSTAGE_HOST=$(kubectl get ingress backstage --kubeconfig kubeconfig -o json | jq '.spec.rules[0].host' | tr -d '"')
          export UFFIZZI_CLUSTER_APISERVER=$(kubectl config view --minify | grep server | cut -f 2- -d ":" | tr -d " ")
          # Patch backstage deployment to use UFFIZZI_URL
          kubectl patch deployment backstage --kubeconfig kubeconfig  -p '{"spec": {"template": {"spec": {"containers": [{"name": "backstage", "args":["-c", "APP_CONFIG_app_baseUrl='https://${BACKSTAGE_HOST}' APP_CONFIG_backend_baseUrl='https://${BACKSTAGE_HOST}' APP_CONFIG_auth_environment='production' node packages/backend --config app-config.yaml"], "env": [{"name": "UFFIZZI_URL", "value": "'https://${BACKSTAGE_HOST}'"}, {"name": "UFFIZZI_CLUSTER_APISERVER", "value": "'${UFFIZZI_CLUSTER_APISERVER}'"}, {"name": "GITHUB_SHA", "value": "'${GITHUB_SHA}'"}, {"name": "REF_NAME", "value": "'${{ needs.cache-manifests-file.outputs.git-ref }}'"}]}]}}}}'
          if [[ ${RUNNER_DEBUG} == 1 ]]; then
            kubectl get all --kubeconfig ./kubeconfig
          fi
          echo "backstage_url=${BACKSTAGE_HOST}" >> $GITHUB_OUTPUT
          echo "Access the \`backstage\` endpoint at [\`${BACKSTAGE_HOST}\`](http://${BACKSTAGE_HOST})" >> $GITHUB_STEP_SUMMARY

      - name: Create or Update Comment with Deployment URL
        uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4
        with:
          comment-id: ${{ steps.notification.outputs.comment-id }}
          issue-number: ${{ github.event.pull_request.number }}
          body: |
            ## Uffizzi Ephemeral Environment - Virtual Cluster

            Your cluster `pr-${{ needs.cache-manifests-file.outputs.pr-number }}` was successfully created. Learn more about [Uffizzi virtual clusters](https://docs.uffizzi.com/topics/virtual-clusters)
            To connect to this cluster, follow these steps:

            1. Download and install the Uffizzi CLI from https://docs.uffizzi.com/install
            2. Login to Uffizzi, then select the `backstage` account and project:
            ```
            uffizzi login
            ```

            ```
            Select an account: 
              ‣ ${{ github.event.repository.name }}
                jdoe

            Select a project or create a new project: 
              ‣ ${{ github.event.repository.name }}-6783521
            ```
            3. Update your kubeconfig: `uffizzi cluster update-kubeconfig pr-${{ needs.cache-manifests-file.outputs.pr-number }} --kubeconfig=[PATH_TO_KUBECONFIG]`
            After updating your kubeconfig, you can manage your cluster with `kubectl`, `kustomize`, `helm`, and other tools that use kubeconfig files: `kubectl get namespace --kubeconfig [PATH_TO_KUBECONFIG]`


            Access the `backstage` endpoint at [`https://${{ steps.prev.outputs.backstage_url }}`](https://${{ steps.prev.outputs.backstage_url }})

          edit-mode: replace

  delete-uffizzi-preview:
    permissions:
      contents: read
      pull-requests: write
      id-token: write
    name: Delete the Uffizzi Virtual Cluster
    needs:
      - cache-manifests-file
    if: ${{ needs.cache-manifests-file.outputs.action == 'closed' }}
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4

      - name: Delete Virtual Cluster
        uses: UffizziCloud/cluster-action@main
        with:
          cluster-name: pr-${{ needs.cache-manifests-file.outputs.pr-number }}
          server: https://app.uffizzi.com
          action: delete

      # Identify comment to be updated
      - name: Find comment for Ephemeral Environment
        uses: peter-evans/find-comment@d5fe37641ad8451bdd80312415672ba26c86575e # v3
        id: find-comment
        with:
          issue-number: ${{ needs.cache-manifests-file.outputs.pr-number }}
          comment-author: 'github-actions[bot]'
          body-includes: pr-${{ needs.cache-manifests-file.outputs.pr-number }}
          direction: last

      - name: Update Comment with Deletion
        uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4
        with:
          comment-id: ${{ steps.find-comment.outputs.comment-id }}
          issue-number: ${{ needs.cache-manifests-file.outputs.pr-number }}
          body: |
            Uffizzi Cluster `pr-${{ needs.cache-manifests-file.outputs.pr-number }}` was deleted.
          edit-mode: replace
